/** @file
DnsDxe support functions implementation.
  
Copyright (c) 2015, Intel Corporation. All rights reserved.<BR>
This program and the accompanying materials
are licensed and made available under the terms and conditions of the BSD License
which accompanies this distribution.  The full text of the license may be found at
http://opensource.org/licenses/bsd-license.php

THE PROGRAM IS DISTRIBUTED UNDER THE BSD LICENSE ON AN "AS IS" BASIS,
WITHOUT WARRANTIES OR REPRESENTATIONS OF ANY KIND, EITHER EXPRESS OR IMPLIED.

**/

#include "DnsImpl.h"

/**
  Remove TokenEntry from TokenMap.

  @param[in] TokenMap          All DNSv4 Token entrys.
  @param[in] TokenEntry        TokenEntry need to be removed.

  @retval EFI_SUCCESS          Remove TokenEntry from TokenMap sucessfully.
  @retval EFI_NOT_FOUND        TokenEntry is not found in TokenMap.

**/
EFI_STATUS
Dns4RemoveTokenEntry (
  IN NET_MAP                    *TokenMap,
  IN DNS4_TOKEN_ENTRY           *TokenEntry
  )
{
  NET_MAP_ITEM  *Item;

  //
  // Find the TokenEntry first.
  //
  Item = NetMapFindKey (TokenMap, (VOID *) TokenEntry);

  if (Item != NULL) {
    //
    // Remove the TokenEntry if it's found in the map.
    //
    NetMapRemoveItem (TokenMap, Item, NULL);

    return EFI_SUCCESS;
  }
  
  return EFI_NOT_FOUND;
}

/**
  Remove TokenEntry from TokenMap.

  @param[in] TokenMap           All DNSv6 Token entrys.
  @param[in] TokenEntry         TokenEntry need to be removed.

  @retval EFI_SUCCESS           Remove TokenEntry from TokenMap sucessfully.
  @retval EFI_NOT_FOUND         TokenEntry is not found in TokenMap.
  
**/
EFI_STATUS
Dns6RemoveTokenEntry (
  IN NET_MAP                    *TokenMap,
  IN DNS6_TOKEN_ENTRY           *TokenEntry
  )
{
  NET_MAP_ITEM  *Item;

  //
  // Find the TokenEntry first.
  //
  Item = NetMapFindKey (TokenMap, (VOID *) TokenEntry);

  if (Item != NULL) {
    //
    // Remove the TokenEntry if it's found in the map.
    //
    NetMapRemoveItem (TokenMap, Item, NULL);

    return EFI_SUCCESS;
  }
  
  return EFI_NOT_FOUND;
}

/**
  This function cancle the token specified by Arg in the Map.

  @param[in]  Map             Pointer to the NET_MAP.
  @param[in]  Item            Pointer to the NET_MAP_ITEM.
  @param[in]  Arg             Pointer to the token to be cancelled. If NULL, all
                              the tokens in this Map will be cancelled.
                              This parameter is optional and may be NULL.

  @retval EFI_SUCCESS         The token is cancelled if Arg is NULL, or the token
                              is not the same as that in the Item, if Arg is not
                              NULL.
  @retval EFI_ABORTED         Arg is not NULL, and the token specified by Arg is
                              cancelled.

**/
EFI_STATUS
EFIAPI
Dns4CancelTokens (
  IN NET_MAP       *Map,
  IN NET_MAP_ITEM  *Item,
  IN VOID          *Arg OPTIONAL
  )
{
  DNS4_TOKEN_ENTRY           *TokenEntry;
  NET_BUF                    *Packet;
  UDP_IO                     *UdpIo;

  if ((Arg != NULL) && (Item->Key != Arg)) {
    return EFI_SUCCESS;
  }

  if (Item->Value != NULL) {
    //
    // If the TokenEntry is a transmit TokenEntry, the corresponding Packet is recorded in
    // Item->Value.
    //
    Packet  = (NET_BUF *) (Item->Value);
    UdpIo = (UDP_IO *) (*((UINTN *) &Packet->ProtoData[0]));

    UdpIoCancelSentDatagram (UdpIo, Packet);
  }

  //
  // Remove TokenEntry from Dns4TxTokens.
  //
  TokenEntry = (DNS4_TOKEN_ENTRY *) Item->Key;
  if (Dns4RemoveTokenEntry (Map, TokenEntry) == EFI_SUCCESS) {
    TokenEntry->Token->Status = EFI_ABORTED;
    gBS->SignalEvent (TokenEntry->Token->Event);
    DispatchDpc ();
  }

  if (Arg != NULL) {
    return EFI_ABORTED;
  }

  return EFI_SUCCESS;
}

/**
  This function cancle the token specified by Arg in the Map.

  @param[in]  Map             Pointer to the NET_MAP.
  @param[in]  Item            Pointer to the NET_MAP_ITEM.
  @param[in]  Arg             Pointer to the token to be cancelled. If NULL, all
                              the tokens in this Map will be cancelled.
                              This parameter is optional and may be NULL.

  @retval EFI_SUCCESS         The token is cancelled if Arg is NULL, or the token
                              is not the same as that in the Item, if Arg is not
                              NULL.
  @retval EFI_ABORTED         Arg is not NULL, and the token specified by Arg is
                              cancelled.

**/
EFI_STATUS
EFIAPI
Dns6CancelTokens (
  IN NET_MAP       *Map,
  IN NET_MAP_ITEM  *Item,
  IN VOID          *Arg OPTIONAL
  )
{
  DNS6_TOKEN_ENTRY           *TokenEntry;
  NET_BUF                    *Packet;
  UDP_IO                     *UdpIo;

  if ((Arg != NULL) && (Item->Key != Arg)) {
    return EFI_SUCCESS;
  }

  if (Item->Value != NULL) {
    //
    // If the TokenEntry is a transmit TokenEntry, the corresponding Packet is recorded in
    // Item->Value.
    //
    Packet  = (NET_BUF *) (Item->Value);
    UdpIo = (UDP_IO *) (*((UINTN *) &Packet->ProtoData[0]));

    UdpIoCancelSentDatagram (UdpIo, Packet);
  }

  //
  // Remove TokenEntry from Dns6TxTokens.
  //
  TokenEntry = (DNS6_TOKEN_ENTRY *) Item->Key;
  if (Dns6RemoveTokenEntry (Map, TokenEntry) == EFI_SUCCESS) {
    TokenEntry->Token->Status = EFI_ABORTED;
    gBS->SignalEvent (TokenEntry->Token->Event);
    DispatchDpc ();
  }

  if (Arg != NULL) {
    return EFI_ABORTED;
  }

  return EFI_SUCCESS;
}

/**
  Get the TokenEntry from the TokensMap.

  @param[in]  TokensMap           All DNSv4 Token entrys
  @param[in]  Token               Pointer to the token to be get.
  @param[out] TokenEntry          Pointer to TokenEntry corresponding Token.

  @retval EFI_SUCCESS             Get the TokenEntry from the TokensMap sucessfully.
  @retval EFI_NOT_FOUND           TokenEntry is not found in TokenMap.

**/
EFI_STATUS
EFIAPI
GetDns4TokenEntry (
  IN     NET_MAP                   *TokensMap,
  IN     EFI_DNS4_COMPLETION_TOKEN *Token, 
     OUT DNS4_TOKEN_ENTRY          **TokenEntry
  )
{
  LIST_ENTRY              *Entry;
  
  NET_MAP_ITEM            *Item;
  
  NET_LIST_FOR_EACH (Entry, &TokensMap->Used) {
    Item = NET_LIST_USER_STRUCT (Entry, NET_MAP_ITEM, Link);
    *TokenEntry = (DNS4_TOKEN_ENTRY *) (Item->Key);  
    if ((*TokenEntry)->Token == Token) {
      return EFI_SUCCESS;
    }
  }
  
  *TokenEntry = NULL;
  
  return EFI_NOT_FOUND;
}

/**
  Get the TokenEntry from the TokensMap.

  @param[in]  TokensMap           All DNSv6 Token entrys
  @param[in]  Token               Pointer to the token to be get.
  @param[out] TokenEntry          Pointer to TokenEntry corresponding Token.

  @retval EFI_SUCCESS             Get the TokenEntry from the TokensMap sucessfully.
  @retval EFI_NOT_FOUND           TokenEntry is not found in TokenMap.

**/
EFI_STATUS
EFIAPI
GetDns6TokenEntry (
  IN     NET_MAP                   *TokensMap,
  IN     EFI_DNS6_COMPLETION_TOKEN *Token, 
     OUT DNS6_TOKEN_ENTRY          **TokenEntry
  )
{
  LIST_ENTRY              *Entry;
  
  NET_MAP_ITEM            *Item;
  
  NET_LIST_FOR_EACH (Entry, &TokensMap->Used) {
    Item = NET_LIST_USER_STRUCT (Entry, NET_MAP_ITEM, Link);
    *TokenEntry = (DNS6_TOKEN_ENTRY *) (Item->Key);  
    if ((*TokenEntry)->Token == Token) {
      return EFI_SUCCESS;
    }
  }
  
  *TokenEntry =NULL;
  
  return EFI_NOT_FOUND;
}

/**
  Cancel DNS4 tokens from the DNS4 instance.

  @param[in]  Instance           Pointer to the DNS instance context data.
  @param[in]  Token              Pointer to the token to be canceled. If NULL, all
                                 tokens in this instance will be cancelled.
                                 This parameter is optional and may be NULL.

  @retval EFI_SUCCESS            The Token is cancelled.
  @retval EFI_NOT_FOUND          The Token is not found.

**/
EFI_STATUS
Dns4InstanceCancelToken (
  IN DNS_INSTANCE               *Instance,
  IN EFI_DNS4_COMPLETION_TOKEN  *Token
  )
{
  EFI_STATUS        Status;
  DNS4_TOKEN_ENTRY  *TokenEntry;

  TokenEntry = NULL;

  if(Token != NULL  ) {
    Status = GetDns4TokenEntry (&Instance->Dns4TxTokens, Token, &TokenEntry);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  } else {
    TokenEntry = NULL;
  }

  //
  // Cancel this TokenEntry from the Dns4TxTokens map.
  //
  Status = NetMapIterate (&Instance->Dns4TxTokens, Dns4CancelTokens, TokenEntry);

  if ((TokenEntry != NULL) && (Status == EFI_ABORTED)) {
    //
    // If Token isn't NULL and Status is EFI_ABORTED, the token is cancelled from
    // the Dns4TxTokens and returns success.
    //
    if (NetMapIsEmpty (&Instance->Dns4TxTokens)) {
       Instance->UdpIo->Protocol.Udp4->Cancel (Instance->UdpIo->Protocol.Udp4, &Instance->UdpIo->RecvRequest->Token.Udp4);
    }
    return EFI_SUCCESS;
  }

  ASSERT ((TokenEntry != NULL) || (0 == NetMapGetCount (&Instance->Dns4TxTokens)));
  
  if (NetMapIsEmpty (&Instance->Dns4TxTokens)) {
    Instance->UdpIo->Protocol.Udp4->Cancel (Instance->UdpIo->Protocol.Udp4, &Instance->UdpIo->RecvRequest->Token.Udp4);
  }

  return EFI_SUCCESS;
}

/**
  Cancel DNS6 tokens from the DNS6 instance.

  @param[in]  Instance           Pointer to the DNS instance context data.
  @param[in]  Token              Pointer to the token to be canceled. If NULL, all
                                 tokens in this instance will be cancelled.
                                 This parameter is optional and may be NULL.

  @retval EFI_SUCCESS            The Token is cancelled.
  @retval EFI_NOT_FOUND          The Token is not found.

**/
EFI_STATUS
Dns6InstanceCancelToken (
  IN DNS_INSTANCE               *Instance,
  IN EFI_DNS6_COMPLETION_TOKEN  *Token
  )
{
  EFI_STATUS        Status;
  DNS6_TOKEN_ENTRY  *TokenEntry;

  TokenEntry = NULL;

  if(Token != NULL  ) {
    Status = GetDns6TokenEntry (&Instance->Dns6TxTokens, Token, &TokenEntry);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  } else {
    TokenEntry = NULL;
  }

  //
  // Cancel this TokenEntry from the Dns6TxTokens map.
  //
  Status = NetMapIterate (&Instance->Dns6TxTokens, Dns6CancelTokens, TokenEntry);

  if ((TokenEntry != NULL) && (Status == EFI_ABORTED)) {
    //
    // If Token isn't NULL and Status is EFI_ABORTED, the token is cancelled from
    // the Dns6TxTokens and returns success.
    //
    if (NetMapIsEmpty (&Instance->Dns6TxTokens)) {
       Instance->UdpIo->Protocol.Udp6->Cancel (Instance->UdpIo->Protocol.Udp6, &Instance->UdpIo->RecvRequest->Token.Udp6);
    }
    return EFI_SUCCESS;
  }

  ASSERT ((TokenEntry != NULL) || (0 == NetMapGetCount (&Instance->Dns6TxTokens)));
  
  if (NetMapIsEmpty (&Instance->Dns6TxTokens)) {
    Instance->UdpIo->Protocol.Udp6->Cancel (Instance->UdpIo->Protocol.Udp6, &Instance->UdpIo->RecvRequest->Token.Udp6);
  }

  return EFI_SUCCESS;
}

/**
  Free the resource related to the configure parameters.

  @param  Config                 The DNS configure data

**/
VOID
Dns4CleanConfigure (
  IN OUT EFI_DNS4_CONFIG_DATA  *Config
  )
{
  if (Config->DnsServerList != NULL) {
    FreePool (Config->DnsServerList);
  }

  ZeroMem (Config, sizeof (EFI_DNS4_CONFIG_DATA));
}

/**
  Free the resource related to the configure parameters.

  @param  Config                 The DNS configure data

**/
VOID
Dns6CleanConfigure (
  IN OUT EFI_DNS6_CONFIG_DATA  *Config
  )
{
  if (Config->DnsServerList != NULL) {
    FreePool (Config->DnsServerList);
  }

  ZeroMem (Config, sizeof (EFI_DNS6_CONFIG_DATA));
}

/**
  Allocate memory for configure parameter such as timeout value for Dst,
  then copy the configure parameter from Src to Dst.

  @param[out]  Dst               The destination DHCP configure data.
  @param[in]   Src               The source DHCP configure data.

  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory.
  @retval EFI_SUCCESS            The configure is copied.

**/
EFI_STATUS
Dns4CopyConfigure (
  OUT EFI_DNS4_CONFIG_DATA  *Dst,
  IN  EFI_DNS4_CONFIG_DATA  *Src
  )
{
  UINTN                     Len;
  UINT32                    Index;

  CopyMem (Dst, Src, sizeof (*Dst));
  Dst->DnsServerList = NULL;

  //
  // Allocate a memory then copy DnsServerList to it
  //
  if (Src->DnsServerList != NULL) {
    Len                = Src->DnsServerListCount * sizeof (EFI_IPv4_ADDRESS);
    Dst->DnsServerList = AllocatePool (Len);
    if (Dst->DnsServerList == NULL) {
      Dns4CleanConfigure (Dst);
      return EFI_OUT_OF_RESOURCES;
    }

    for (Index = 0; Index < Src->DnsServerListCount; Index++) {
      CopyMem (&Dst->DnsServerList[Index], &Src->DnsServerList[Index], sizeof (EFI_IPv4_ADDRESS));
    }
  }

  return EFI_SUCCESS;
}

/**
  Allocate memory for configure parameter such as timeout value for Dst,
  then copy the configure parameter from Src to Dst.

  @param[out]  Dst               The destination DHCP configure data.
  @param[in]   Src               The source DHCP configure data.

  @retval EFI_OUT_OF_RESOURCES   Failed to allocate memory.
  @retval EFI_SUCCESS            The configure is copied.

**/
EFI_STATUS
Dns6CopyConfigure (
  OUT EFI_DNS6_CONFIG_DATA  *Dst,
  IN  EFI_DNS6_CONFIG_DATA  *Src
  )
{
  UINTN                     Len;
  UINT32                    Index;

  CopyMem (Dst, Src, sizeof (*Dst));
  Dst->DnsServerList = NULL;

  //
  // Allocate a memory then copy DnsServerList to it
  //
  if (Src->DnsServerList != NULL) {
    Len                = Src->DnsServerCount * sizeof (EFI_IPv6_ADDRESS);
    Dst->DnsServerList = AllocatePool (Len);
    if (Dst->DnsServerList == NULL) {
      Dns6CleanConfigure (Dst);
      return EFI_OUT_OF_RESOURCES;
    }

    for (Index = 0; Index < Src->DnsServerCount; Index++) {
      CopyMem (&Dst->DnsServerList[Index], &Src->DnsServerList[Index], sizeof (EFI_IPv6_ADDRESS));
    }
  }

  return EFI_SUCCESS;
}

/**
  Callback of Dns packet. Does nothing.

  @param Arg           The context.

**/
VOID
EFIAPI
DnsDummyExtFree (
  IN VOID                   *Arg
  )
{
}

/**
  Poll the UDP to get the IP4 default address, which may be retrieved
  by DHCP. 
  
  The default time out value is 5 seconds. If IP has retrieved the default address, 
  the UDP is reconfigured.

  @param  Instance               The DNS instance
  @param  UdpIo                  The UDP_IO to poll
  @param  UdpCfgData             The UDP configure data to reconfigure the UDP_IO

  @retval TRUE                   The default address is retrieved and UDP is reconfigured.
  @retval FALSE                  Some error occured.

**/
BOOLEAN
Dns4GetMapping (
  IN DNS_INSTANCE           *Instance,
  IN UDP_IO                 *UdpIo,
  IN EFI_UDP4_CONFIG_DATA   *UdpCfgData
  )
{
  DNS_SERVICE               *Service;
  EFI_IP4_MODE_DATA         Ip4Mode;
  EFI_UDP4_PROTOCOL         *Udp;
  EFI_STATUS                Status;

  ASSERT (Instance->Dns4CfgData.UseDefaultSetting);

  Service = Instance->Service;
  Udp     = UdpIo->Protocol.Udp4;

  Status = gBS->SetTimer (
                  Service->TimerToGetMap,
                  TimerRelative,
                  DNS_TIME_TO_GETMAP * TICKS_PER_SECOND
                  );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  while (!EFI_ERROR (gBS->CheckEvent (Service->TimerToGetMap))) {
    Udp->Poll (Udp);

    if (!EFI_ERROR (Udp->GetModeData (Udp, NULL, &Ip4Mode, NULL, NULL)) &&
        Ip4Mode.IsConfigured) {

      Udp->Configure (Udp, NULL);
      return (BOOLEAN) (Udp->Configure (Udp, UdpCfgData) == EFI_SUCCESS);
    }
  }

  return FALSE;
}

/**
  Configure the opened Udp6 instance until the corresponding Ip6 instance
  has been configured.

  @param  Instance               The DNS instance
  @param  UdpIo                  The UDP_IO to poll
  @param  UdpCfgData             The UDP configure data to reconfigure the UDP_IO

  @retval TRUE                   Configure the Udp6 instance successfully.
  @retval FALSE                  Some error occured.

**/
BOOLEAN
Dns6GetMapping (
  IN DNS_INSTANCE           *Instance,
  IN UDP_IO                 *UdpIo,
  IN EFI_UDP6_CONFIG_DATA   *UdpCfgData
  )
{
  DNS_SERVICE               *Service;
  EFI_IP6_MODE_DATA         Ip6Mode;
  EFI_UDP6_PROTOCOL         *Udp;
  EFI_STATUS                Status;

  Service = Instance->Service;
  Udp     = UdpIo->Protocol.Udp6;

  Status = gBS->SetTimer (
                  Service->TimerToGetMap,
                  TimerRelative,
                  DNS_TIME_TO_GETMAP * TICKS_PER_SECOND
                  );
  if (EFI_ERROR (Status)) {
    return FALSE;
  }

  while (!EFI_ERROR (gBS->CheckEvent (Service->TimerToGetMap))) {
    Udp->Poll (Udp);

    if (!EFI_ERROR (Udp->GetModeData (Udp, NULL, &Ip6Mode, NULL, NULL)) &&
        Ip6Mode.IsConfigured) {

      Udp->Configure (Udp, NULL);
      return (BOOLEAN) (Udp->Configure (Udp, UdpCfgData) == EFI_SUCCESS);
    }
  }

  return FALSE;
}

/**
  Configure the UDP.
  
  @param  Instance               The DNS session
  @param  UdpIo                  The UDP_IO instance
  
  @retval EFI_SUCCESS            The UDP is successfully configured for the
                                 session.

**/
EFI_STATUS
Dns4ConfigUdp (
  IN DNS_INSTANCE           *Instance,
  IN UDP_IO                 *UdpIo
  )
{
  EFI_DNS4_CONFIG_DATA      *Config;
  EFI_UDP4_CONFIG_DATA      UdpConfig;
  EFI_STATUS                Status;

  Config = &Instance->Dns4CfgData;

  UdpConfig.AcceptBroadcast    = FALSE;
  UdpConfig.AcceptPromiscuous  = FALSE;
  UdpConfig.AcceptAnyPort      = FALSE;
  UdpConfig.AllowDuplicatePort = FALSE;
  UdpConfig.TypeOfService      = 0;
  UdpConfig.TimeToLive         = 128;
  UdpConfig.DoNotFragment      = FALSE;
  UdpConfig.ReceiveTimeout     = 0;
  UdpConfig.TransmitTimeout    = 0;
  UdpConfig.UseDefaultAddress  = Config->UseDefaultSetting;
  UdpConfig.SubnetMask         = Config->SubnetMask;
  UdpConfig.StationPort        = Config->LocalPort;
  UdpConfig.RemotePort         = DNS_SERVER_PORT;

  CopyMem (&UdpConfig.StationAddress, &Config->StationIp, sizeof (EFI_IPv4_ADDRESS));
  CopyMem (&UdpConfig.RemoteAddress, &Instance->SessionDnsServer.v4, sizeof (EFI_IPv4_ADDRESS));

  Status = UdpIo->Protocol.Udp4->Configure (UdpIo->Protocol.Udp4, &UdpConfig);

  if ((Status == EFI_NO_MAPPING) && Dns4GetMapping (Instance, UdpIo, &UdpConfig)) {
    return EFI_SUCCESS;
  }
  
  return Status;
}

/**
  Configure the UDP.
  
  @param  Instance               The DNS session
  @param  UdpIo                  The UDP_IO instance

  @retval EFI_SUCCESS            The UDP is successfully configured for the
                                 session.

**/
EFI_STATUS
Dns6ConfigUdp (
  IN DNS_INSTANCE           *Instance,
  IN UDP_IO                 *UdpIo
  )
{
  EFI_DNS6_CONFIG_DATA      *Config;
  EFI_UDP6_CONFIG_DATA      UdpConfig;
  EFI_STATUS                Status;

  Config = &Instance->Dns6CfgData;

  UdpConfig.AcceptPromiscuous  = FALSE;
  UdpConfig.AcceptAnyPort      = FALSE;
  UdpConfig.AllowDuplicatePort = FALSE;
  UdpConfig.TrafficClass       = 0;
  UdpConfig.HopLimit           = 128;
  UdpConfig.ReceiveTimeout     = 0;
  UdpConfig.TransmitTimeout    = 0;
  UdpConfig.StationPort        = Config->LocalPort;
  UdpConfig.RemotePort         = DNS_SERVER_PORT;
  CopyMem (&UdpConfig.StationAddress, &Config->StationIp, sizeof (EFI_IPv6_ADDRESS));
  CopyMem (&UdpConfig.RemoteAddress, &Instance->SessionDnsServer.v6, sizeof (EFI_IPv6_ADDRESS));

  Status = UdpIo->Protocol.Udp6->Configure (UdpIo->Protocol.Udp6, &UdpConfig);

  if ((Status == EFI_NO_MAPPING) && Dns6GetMapping (Instance, UdpIo, &UdpConfig)) {
    return EFI_SUCCESS;
  }
  
  return Status;
}

/**
  Update Dns4 cache to shared list of caches of all DNSv4 instances.
  
  @param  Dns4CacheList      All Dns4 cache list.
  @param  DeleteFlag         If FALSE, this function is to add one entry to the DNS Cache. 
                             If TRUE, this function will delete matching DNS Cache entry. 
  @param  Override           If TRUE, the matching DNS cache entry will be overwritten with the supplied parameter. 
                             If FALSE, EFI_ACCESS_DENIED will be returned if the entry to be added is already exists.
  @param  DnsCacheEntry      Entry Pointer to DNS Cache entry.

  @retval EFI_SUCCESS        Update Dns4 cache successfully.
  @retval Others             Failed to update Dns4 cache.   
  
**/ 
EFI_STATUS
EFIAPI
UpdateDns4Cache (
  IN LIST_ENTRY             *Dns4CacheList,
  IN BOOLEAN                DeleteFlag,
  IN BOOLEAN                Override,
  IN EFI_DNS4_CACHE_ENTRY   DnsCacheEntry
  )
{
  DNS4_CACHE    *NewDnsCache;  
  DNS4_CACHE    *Item;
  LIST_ENTRY    *Entry;
  LIST_ENTRY    *Next;

  NewDnsCache = NULL;
  Item        = NULL;
  
  //
  // Search the database for the matching EFI_DNS_CACHE_ENTRY
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, Dns4CacheList) {
    Item = NET_LIST_USER_STRUCT (Entry, DNS4_CACHE, AllCacheLink);
    if (StrCmp (DnsCacheEntry.HostName, Item->DnsCache.HostName) == 0 && \
        CompareMem (DnsCacheEntry.IpAddress, Item->DnsCache.IpAddress, sizeof (EFI_IPv4_ADDRESS)) == 0) {
      //
      // This is the Dns cache entry
      //
      if (DeleteFlag) {
        //
        // Delete matching DNS Cache entry
        //
        RemoveEntryList (&Item->AllCacheLink);
        
        return EFI_SUCCESS;
      } else if (Override) {
        //
        // Update this one
        //
        Item->DnsCache.Timeout = DnsCacheEntry.Timeout;
        
        return EFI_SUCCESS;
      }else {
        return EFI_ACCESS_DENIED;
      }
    }
  }

  //
  // Add new one
  //
  NewDnsCache = AllocatePool (sizeof (DNS4_CACHE));
  if (NewDnsCache == NULL) { 
    return EFI_OUT_OF_RESOURCES;
  }
  
  InitializeListHead (&NewDnsCache->AllCacheLink);
   
  NewDnsCache->DnsCache.HostName = AllocatePool (StrSize (DnsCacheEntry.HostName));
  if (NewDnsCache->DnsCache.HostName == NULL) { 
    return EFI_OUT_OF_RESOURCES;
  }
  
  CopyMem (NewDnsCache->DnsCache.HostName, DnsCacheEntry.HostName, StrSize (DnsCacheEntry.HostName));

  NewDnsCache->DnsCache.IpAddress = AllocatePool (sizeof (EFI_IPv4_ADDRESS));
  if (NewDnsCache->DnsCache.IpAddress == NULL) { 
    return EFI_OUT_OF_RESOURCES;
  }

  CopyMem (NewDnsCache->DnsCache.IpAddress, DnsCacheEntry.IpAddress, sizeof (EFI_IPv4_ADDRESS));

  NewDnsCache->DnsCache.Timeout = DnsCacheEntry.Timeout;
  
  InsertTailList (Dns4CacheList, &NewDnsCache->AllCacheLink);
  
  return EFI_SUCCESS;
}

/**
  Update Dns6 cache to shared list of caches of all DNSv6 instances. 

  @param  Dns6CacheList      All Dns6 cache list.
  @param  DeleteFlag         If FALSE, this function is to add one entry to the DNS Cache. 
                             If TRUE, this function will delete matching DNS Cache entry. 
  @param  Override           If TRUE, the matching DNS cache entry will be overwritten with the supplied parameter. 
                             If FALSE, EFI_ACCESS_DENIED will be returned if the entry to be added is already exists.
  @param  DnsCacheEntry      Entry Pointer to DNS Cache entry.
  
  @retval EFI_SUCCESS        Update Dns6 cache successfully.
  @retval Others             Failed to update Dns6 cache.
**/ 
EFI_STATUS
EFIAPI
UpdateDns6Cache (
  IN LIST_ENTRY             *Dns6CacheList,
  IN BOOLEAN                DeleteFlag,
  IN BOOLEAN                Override,
  IN EFI_DNS6_CACHE_ENTRY   DnsCacheEntry
  )
{
  DNS6_CACHE    *NewDnsCache;  
  DNS6_CACHE    *Item;
  LIST_ENTRY    *Entry;
  LIST_ENTRY    *Next;

  NewDnsCache = NULL;
  Item        = NULL;
  
  //
  // Search the database for the matching EFI_DNS_CACHE_ENTRY
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, Dns6CacheList) {
    Item = NET_LIST_USER_STRUCT (Entry, DNS6_CACHE, AllCacheLink);
    if (StrCmp (DnsCacheEntry.HostName, Item->DnsCache.HostName) == 0 && \
        CompareMem (DnsCacheEntry.IpAddress, Item->DnsCache.IpAddress, sizeof (EFI_IPv6_ADDRESS)) == 0) {
      //
      // This is the Dns cache entry
      //
      if (DeleteFlag) {
        //
        // Delete matching DNS Cache entry
        //
        RemoveEntryList (&Item->AllCacheLink);
        
        return EFI_SUCCESS;
      } else if (Override) {
        //
        // Update this one
        //
        Item->DnsCache.Timeout = DnsCacheEntry.Timeout;
        
        return EFI_SUCCESS;
      }else {
        return EFI_ACCESS_DENIED;
      }
    }
  }

  //
  // Add new one
  //
  NewDnsCache = AllocatePool (sizeof (DNS6_CACHE));
  if (NewDnsCache == NULL) { 
    return EFI_OUT_OF_RESOURCES;
  }
  
  InitializeListHead (&NewDnsCache->AllCacheLink);
   
  NewDnsCache->DnsCache.HostName = AllocatePool (StrSize (DnsCacheEntry.HostName));
  if (NewDnsCache->DnsCache.HostName == NULL) { 
    return EFI_OUT_OF_RESOURCES;
  }
  
  CopyMem (NewDnsCache->DnsCache.HostName, DnsCacheEntry.HostName, StrSize (DnsCacheEntry.HostName));

  NewDnsCache->DnsCache.IpAddress = AllocatePool (sizeof (EFI_IPv6_ADDRESS));
  if (NewDnsCache->DnsCache.IpAddress == NULL) { 
    return EFI_OUT_OF_RESOURCES;
  }
  
  CopyMem (NewDnsCache->DnsCache.IpAddress, DnsCacheEntry.IpAddress, sizeof (EFI_IPv6_ADDRESS));

  NewDnsCache->DnsCache.Timeout = DnsCacheEntry.Timeout;
  
  InsertTailList (Dns6CacheList, &NewDnsCache->AllCacheLink);
  
  return EFI_SUCCESS;
}

/**
  Add Dns4 ServerIp to common list of addresses of all configured DNSv4 server. 

  @param  Dns4ServerList    Common list of addresses of all configured DNSv4 server.  
  @param  ServerIp          DNS server Ip.  

  @retval EFI_SUCCESS       Add Dns4 ServerIp to common list successfully.
  @retval Others            Failed to add Dns4 ServerIp to common list.
  
**/ 
EFI_STATUS
EFIAPI
AddDns4ServerIp (
  IN LIST_ENTRY                *Dns4ServerList,
  IN EFI_IPv4_ADDRESS           ServerIp
  )
{
  DNS4_SERVER_IP    *NewServerIp;  
  DNS4_SERVER_IP    *Item;
  LIST_ENTRY        *Entry;
  LIST_ENTRY        *Next;

  NewServerIp = NULL;
  Item        = NULL;
  
  //
  // Search the database for the matching ServerIp
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, Dns4ServerList) {
    Item = NET_LIST_USER_STRUCT (Entry, DNS4_SERVER_IP, AllServerLink);
    if (CompareMem (&Item->Dns4ServerIp, &ServerIp, sizeof (EFI_IPv4_ADDRESS)) == 0) {
      //
      // Already done.
      // 
      return EFI_SUCCESS;
    }
  }

  //
  // Add new one
  //
  NewServerIp = AllocatePool (sizeof (DNS4_SERVER_IP));
  if (NewServerIp == NULL) { 
    return EFI_OUT_OF_RESOURCES;
  }
  
  InitializeListHead (&NewServerIp->AllServerLink);
   
  CopyMem (&NewServerIp->Dns4ServerIp, &ServerIp, sizeof (EFI_IPv4_ADDRESS));
  
  InsertTailList (Dns4ServerList, &NewServerIp->AllServerLink);
  
  return EFI_SUCCESS;
}

/**
  Add Dns6 ServerIp to common list of addresses of all configured DNSv6 server. 

  @param  Dns6ServerList    Common list of addresses of all configured DNSv6 server.  
  @param  ServerIp          DNS server Ip.  

  @retval EFI_SUCCESS       Add Dns6 ServerIp to common list successfully.
  @retval Others            Failed to add Dns6 ServerIp to common list.
  
**/ 
EFI_STATUS
EFIAPI
AddDns6ServerIp (
  IN LIST_ENTRY                *Dns6ServerList,
  IN EFI_IPv6_ADDRESS           ServerIp
  )
{
  DNS6_SERVER_IP    *NewServerIp;  
  DNS6_SERVER_IP    *Item;
  LIST_ENTRY        *Entry;
  LIST_ENTRY        *Next;

  NewServerIp = NULL;
  Item        = NULL;
  
  //
  // Search the database for the matching ServerIp
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, Dns6ServerList) {
    Item = NET_LIST_USER_STRUCT (Entry, DNS6_SERVER_IP, AllServerLink);
    if (CompareMem (&Item->Dns6ServerIp, &ServerIp, sizeof (EFI_IPv6_ADDRESS)) == 0) {
      //
      // Already done.
      // 
      return EFI_SUCCESS;
    }
  }

  //
  // Add new one
  //
  NewServerIp = AllocatePool (sizeof (DNS6_SERVER_IP));
  if (NewServerIp == NULL) { 
    return EFI_OUT_OF_RESOURCES;
  }
  
  InitializeListHead (&NewServerIp->AllServerLink);
   
  CopyMem (&NewServerIp->Dns6ServerIp, &ServerIp, sizeof (EFI_IPv6_ADDRESS));
  
  InsertTailList (Dns6ServerList, &NewServerIp->AllServerLink);
  
  return EFI_SUCCESS;
}

/**
  Find out whether the response is valid or invalid.

  @param  TokensMap       All DNS transmittal Tokens entry.  
  @param  Identification  Identification for queried packet.  
  @param  Type            Type for queried packet.
  @param  Item            Return corresponding Token entry.

  @retval TRUE            The response is valid.
  @retval FALSE           The response is invalid.
  
**/ 
BOOLEAN
IsValidDnsResponse (
  IN     NET_MAP      *TokensMap,
  IN     UINT16       Identification,
  IN     UINT16       Type,
     OUT NET_MAP_ITEM **Item
  )
{
  LIST_ENTRY              *Entry;

  NET_BUF                 *Packet;
  UINT8                   *TxString;
  DNS_HEADER              *DnsHeader;
  CHAR8                   *QueryName;
  DNS_QUERY_SECTION       *QuerySection;

  NET_LIST_FOR_EACH (Entry, &TokensMap->Used) {
    *Item = NET_LIST_USER_STRUCT (Entry, NET_MAP_ITEM, Link);
    Packet = (NET_BUF *) ((*Item)->Value);
    if (Packet == NULL){
      
      continue;
    } else {
      TxString = NetbufGetByte (Packet, 0, NULL);
      ASSERT (TxString != NULL);
      DnsHeader = (DNS_HEADER *) TxString;
      QueryName = (CHAR8 *) (TxString + sizeof (*DnsHeader));
      QuerySection = (DNS_QUERY_SECTION *) (QueryName + AsciiStrLen (QueryName) + 1);

      DnsHeader->Identification = NTOHS (DnsHeader->Identification);
      QuerySection->Type = NTOHS (QuerySection->Type);
        
      if (DnsHeader->Identification == Identification && QuerySection->Type == Type) {
        return TRUE;
      }
    } 
  }
  
  *Item =NULL;
  
  return FALSE;
}

/**
  Parse Dns Response.

  @param  Instance              The DNS instance
  @param  RxString              Received buffer.
  @param  Completed             Flag to indicate that Dns response is valid. 
  
  @retval EFI_SUCCESS           Parse Dns Response successfully.
  @retval Others                Failed to parse Dns Response.
  
**/ 
EFI_STATUS
ParseDnsResponse (
  IN OUT DNS_INSTANCE              *Instance,
  IN     UINT8                     *RxString,
     OUT BOOLEAN                   *Completed
  )
{
  DNS_HEADER            *DnsHeader;
  
  CHAR8                 *QueryName;
  DNS_QUERY_SECTION     *QuerySection;
  
  CHAR8                 *AnswerName;
  DNS_ANSWER_SECTION    *AnswerSection;
  UINT8                 *AnswerData;

  NET_MAP_ITEM          *Item;
  DNS4_TOKEN_ENTRY      *Dns4TokenEntry;
  DNS6_TOKEN_ENTRY      *Dns6TokenEntry;
  
  UINT32                IpCount;
  UINT32                AnswerSectionNum;
  
  EFI_IPv4_ADDRESS      *HostAddr4;
  EFI_IPv6_ADDRESS      *HostAddr6;

  EFI_DNS4_CACHE_ENTRY  *Dns4CacheEntry;
  EFI_DNS6_CACHE_ENTRY  *Dns6CacheEntry;

  EFI_STATUS            Status;

  EFI_TPL               OldTpl;
  
  Item             = NULL;
  Dns4TokenEntry   = NULL;
  Dns6TokenEntry   = NULL;
  
  IpCount          = 0;
  AnswerSectionNum = 0;
  
  HostAddr4        = NULL;
  HostAddr6        = NULL;
  
  Dns4CacheEntry   = NULL;
  Dns6CacheEntry   = NULL;

  *Completed       = TRUE;
  Status           = EFI_SUCCESS;
  
  //
  // Get header
  //
  DnsHeader = (DNS_HEADER *) RxString;
  
  DnsHeader->Identification = NTOHS (DnsHeader->Identification);
  DnsHeader->Flags.Uint16 = NTOHS (DnsHeader->Flags.Uint16);
  DnsHeader->QuestionsNum = NTOHS (DnsHeader->QuestionsNum);
  DnsHeader->AnswersNum = NTOHS (DnsHeader->AnswersNum);
  DnsHeader->AuthorityNum = NTOHS (DnsHeader->AuthorityNum);
  DnsHeader->AditionalNum = NTOHS (DnsHeader->AditionalNum);

  //
  // Get Query name
  //
  QueryName = (CHAR8 *) (RxString + sizeof (*DnsHeader));

  //
  // Get query section
  //
  QuerySection = (DNS_QUERY_SECTION *) (QueryName + AsciiStrLen (QueryName) + 1);
  QuerySection->Type = NTOHS (QuerySection->Type);
  QuerySection->Class = NTOHS (QuerySection->Class);

  //
  // Get Answer name
  //
  AnswerName = (CHAR8 *) QuerySection + sizeof (*QuerySection);

  OldTpl = gBS->RaiseTPL (TPL_CALLBACK);

  //
  // Check DnsResponse Validity, if so, also get a valid NET_MAP_ITEM.
  //
  if (Instance->Service->IpVersion == IP_VERSION_4) {
    if (!IsValidDnsResponse (&Instance->Dns4TxTokens, DnsHeader->Identification, QuerySection->Type, &Item)) {
      *Completed = FALSE;
      Status = EFI_ABORTED;
      goto ON_EXIT;
    }
    ASSERT (Item != NULL);
    Dns4TokenEntry = (DNS4_TOKEN_ENTRY *) (Item->Key);
  } else {
    if (!IsValidDnsResponse (&Instance->Dns6TxTokens, DnsHeader->Identification, QuerySection->Type, &Item)) {
      *Completed = FALSE;
      Status = EFI_ABORTED;
      goto ON_EXIT;
    }
    ASSERT (Item != NULL);
    Dns6TokenEntry = (DNS6_TOKEN_ENTRY *) (Item->Key);
  }
   
  //
  // Continue Check Some Errors.
  //
  if (DnsHeader->Flags.Bits.RCode != DNS_FLAGS_RCODE_NO_ERROR || DnsHeader->AnswersNum < 1 || \
      DnsHeader->Flags.Bits.QR != DNS_FLAGS_QR_RESPONSE || QuerySection->Class != DNS_CLASS_INET) {
      Status = EFI_ABORTED;
      goto ON_EXIT;
  }

  //
  // Free the sending packet.
  //
  if (Item->Value != NULL) {
    NetbufFree ((NET_BUF *) (Item->Value));
  }
  
  //
  // Check the Query type, do some buffer allocations.
  //
  if (Instance->Service->IpVersion == IP_VERSION_4) {
    ASSERT (Dns4TokenEntry != NULL);
    if (QuerySection->Type == DNS_TYPE_A) {
      Dns4TokenEntry->Token->RspData.H2AData = AllocatePool (sizeof (DNS_HOST_TO_ADDR_DATA));
      ASSERT (Dns4TokenEntry->Token->RspData.H2AData != NULL);
      Dns4TokenEntry->Token->RspData.H2AData->IpList = AllocatePool (DnsHeader->AnswersNum * sizeof (EFI_IPv4_ADDRESS));
      ASSERT (Dns4TokenEntry->Token->RspData.H2AData->IpList != NULL);
    } else {
      Status = EFI_UNSUPPORTED;
      goto ON_EXIT;
    }
  } else {
    ASSERT (Dns6TokenEntry != NULL);
    if (QuerySection->Type == DNS_TYPE_AAAA) {
      Dns6TokenEntry->Token->RspData.H2AData = AllocatePool (sizeof (DNS6_HOST_TO_ADDR_DATA));
      ASSERT (Dns6TokenEntry->Token->RspData.H2AData != NULL);
      Dns6TokenEntry->Token->RspData.H2AData->IpList = AllocatePool (DnsHeader->AnswersNum * sizeof (EFI_IPv6_ADDRESS));
      ASSERT (Dns6TokenEntry->Token->RspData.H2AData->IpList != NULL);
    } else {
      Status = EFI_UNSUPPORTED;
      goto ON_EXIT;
    }
  }

  //
  // Processing AnswerSection.
  //
  while (AnswerSectionNum < DnsHeader->AnswersNum) {
    //
    // Answer name should be PTR.
    //
    ASSERT ((*(UINT8 *) AnswerName & 0xC0) == 0xC0);
    
    //
    // Get Answer section.
    //
    AnswerSection = (DNS_ANSWER_SECTION *) (AnswerName + sizeof (UINT16));
    AnswerSection->Type = NTOHS (AnswerSection->Type);
    AnswerSection->Class = NTOHS (AnswerSection->Class);
    AnswerSection->Ttl = NTOHL (AnswerSection->Ttl);
    AnswerSection->DataLength = NTOHS (AnswerSection->DataLength);

    ASSERT (AnswerSection->Class == DNS_CLASS_INET);

    if (AnswerSection->Type == QuerySection->Type) {
      switch (AnswerSection->Type) {
      case DNS_TYPE_A:
        //
        // This is address entry, get Data.
        //
        ASSERT (Dns4TokenEntry != NULL && AnswerSection->DataLength == 4);
        
        HostAddr4 = Dns4TokenEntry->Token->RspData.H2AData->IpList;
        AnswerData = (UINT8 *) AnswerSection + sizeof (*AnswerSection);
        CopyMem (&HostAddr4[IpCount], AnswerData, sizeof (EFI_IPv4_ADDRESS));

        //
        // Update DNS cache dynamically.
        //
        if (Dns4CacheEntry != NULL) {
          if (Dns4CacheEntry->HostName != NULL) {
            FreePool (Dns4CacheEntry->HostName);
          }

          if (Dns4CacheEntry->IpAddress != NULL) {
            FreePool (Dns4CacheEntry->IpAddress);
          }
          
          FreePool (Dns4CacheEntry);
        }

        // 
        // Allocate new CacheEntry pool.
        //
        Dns4CacheEntry = AllocateZeroPool (sizeof (EFI_DNS4_CACHE_ENTRY));
        ASSERT (Dns4CacheEntry != NULL);
        Dns4CacheEntry->HostName = AllocateZeroPool (2 * (StrLen(Dns4TokenEntry->QueryHostName) + 1));
        ASSERT (Dns4CacheEntry->HostName != NULL);
        CopyMem (Dns4CacheEntry->HostName, Dns4TokenEntry->QueryHostName, 2 * (StrLen(Dns4TokenEntry->QueryHostName) + 1));
        Dns4CacheEntry->IpAddress = AllocateZeroPool (sizeof (EFI_IPv4_ADDRESS));
        ASSERT (Dns4CacheEntry->IpAddress != NULL);
        CopyMem (Dns4CacheEntry->IpAddress, AnswerData, sizeof (EFI_IPv4_ADDRESS));
        Dns4CacheEntry->Timeout = AnswerSection->Ttl;
        
        UpdateDns4Cache (&mDriverData->Dns4CacheList, FALSE, TRUE, *Dns4CacheEntry);  

        IpCount ++; 
        break;
      case DNS_TYPE_AAAA:
        //
        // This is address entry, get Data.
        //
        ASSERT (Dns6TokenEntry != NULL && AnswerSection->DataLength == 16);
        
        HostAddr6 = Dns6TokenEntry->Token->RspData.H2AData->IpList;
        AnswerData = (UINT8 *) AnswerSection + sizeof (*AnswerSection);
        CopyMem (&HostAddr6[IpCount], AnswerData, sizeof (EFI_IPv6_ADDRESS));

        //
        // Update DNS cache dynamically.
        //
        if (Dns6CacheEntry != NULL) {
          if (Dns6CacheEntry->HostName != NULL) {
            FreePool (Dns6CacheEntry->HostName);
          }

          if (Dns6CacheEntry->IpAddress != NULL) {
            FreePool (Dns6CacheEntry->IpAddress);
          }
          
          FreePool (Dns6CacheEntry);
        }

        // 
        // Allocate new CacheEntry pool.
        //
        Dns6CacheEntry = AllocateZeroPool (sizeof (EFI_DNS6_CACHE_ENTRY));
        ASSERT (Dns6CacheEntry != NULL);
        Dns6CacheEntry->HostName = AllocateZeroPool (2 * (StrLen(Dns6TokenEntry->QueryHostName) + 1));
        ASSERT (Dns6CacheEntry->HostName != NULL);
        CopyMem (Dns6CacheEntry->HostName, Dns6TokenEntry->QueryHostName, 2 * (StrLen(Dns6TokenEntry->QueryHostName) + 1));
        Dns6CacheEntry->IpAddress = AllocateZeroPool (sizeof (EFI_IPv6_ADDRESS));
        ASSERT (Dns6CacheEntry->IpAddress != NULL);
        CopyMem (Dns6CacheEntry->IpAddress, AnswerData, sizeof (EFI_IPv6_ADDRESS));
        Dns6CacheEntry->Timeout = AnswerSection->Ttl;
        
        UpdateDns6Cache (&mDriverData->Dns6CacheList, FALSE, TRUE, *Dns6CacheEntry);  
        
        IpCount ++;
        break;
      default:
        Status = EFI_UNSUPPORTED;
        goto ON_EXIT;
      }
    }
    
    //
    // Find next one
    //
    AnswerName = (CHAR8 *) AnswerSection + sizeof (*AnswerSection) + AnswerSection->DataLength;
    AnswerSectionNum ++;
  }

  if (Instance->Service->IpVersion == IP_VERSION_4) {
    ASSERT (Dns4TokenEntry != NULL);
    if (QuerySection->Type == DNS_TYPE_A) {
      Dns4TokenEntry->Token->RspData.H2AData->IpCount = IpCount;
    } else {
      Status = EFI_UNSUPPORTED;
      goto ON_EXIT;
    }
  } else {
    ASSERT (Dns6TokenEntry != NULL);
    if (QuerySection->Type == DNS_TYPE_AAAA) {
      Dns6TokenEntry->Token->RspData.H2AData->IpCount = IpCount;
    } else {
      Status = EFI_UNSUPPORTED;
      goto ON_EXIT;
    }
  }

  //
  // Parsing is complete, SignalEvent here.
  //
  if (Instance->Service->IpVersion == IP_VERSION_4) {
    ASSERT (Dns4TokenEntry != NULL);
    Dns4RemoveTokenEntry (&Instance->Dns4TxTokens, Dns4TokenEntry);
    Dns4TokenEntry->Token->Status = EFI_SUCCESS;
    if (Dns4TokenEntry->Token->Event != NULL) {
      gBS->SignalEvent (Dns4TokenEntry->Token->Event);
      DispatchDpc ();
    }
  } else {
    ASSERT (Dns6TokenEntry != NULL);
    Dns6RemoveTokenEntry (&Instance->Dns6TxTokens, Dns6TokenEntry);
    Dns6TokenEntry->Token->Status = EFI_SUCCESS;
    if (Dns6TokenEntry->Token->Event != NULL) {
      gBS->SignalEvent (Dns6TokenEntry->Token->Event);
      DispatchDpc ();
    }
  }

  // 
  // Free allocated CacheEntry pool.
  //
  if (Dns4CacheEntry != NULL) {
    if (Dns4CacheEntry->HostName != NULL) {
      FreePool (Dns4CacheEntry->HostName);
    }

    if (Dns4CacheEntry->IpAddress != NULL) {
      FreePool (Dns4CacheEntry->IpAddress);
    }

    FreePool (Dns4CacheEntry);
  }
  
  if (Dns6CacheEntry != NULL) {
    if (Dns6CacheEntry->HostName != NULL) {
      FreePool (Dns6CacheEntry->HostName);
    }

    if (Dns6CacheEntry->IpAddress != NULL) {
      FreePool (Dns6CacheEntry->IpAddress);
    }
  
    FreePool (Dns6CacheEntry);
  }

ON_EXIT:
  gBS->RestoreTPL (OldTpl);
  return Status;
}

/**
  Parse response packet.

  @param  Packet                The packets received.
  @param  EndPoint              The local/remote UDP access point
  @param  IoStatus              The status of the UDP receive
  @param  Context               The opaque parameter to the function.

**/  
VOID
EFIAPI
DnsOnPacketReceived (
  NET_BUF                   *Packet,
  UDP_END_POINT             *EndPoint,
  EFI_STATUS                IoStatus,
  VOID                      *Context
  )
{
  DNS_INSTANCE              *Instance;

  UINT8                     *RcvString;

  BOOLEAN                   Completed;
  
  Instance  = (DNS_INSTANCE *) Context;
  NET_CHECK_SIGNATURE (Instance, DNS_INSTANCE_SIGNATURE);

  RcvString = NULL;
  Completed = FALSE;

  if (EFI_ERROR (IoStatus)) {
    goto ON_EXIT;
  }

  ASSERT (Packet != NULL);
  
  RcvString = NetbufGetByte (Packet, 0, NULL);
  ASSERT (RcvString != NULL);
  
  //
  // Parse Dns Response
  //
  ParseDnsResponse (Instance, RcvString, &Completed);

  ON_EXIT:

    if (Packet != NULL) {
      NetbufFree (Packet);
    }

    if (!Completed) {
      UdpIoRecvDatagram (Instance->UdpIo, DnsOnPacketReceived, Instance, 0);
    }
}

/**
  Release the net buffer when packet is sent.

  @param  Packet                The packets received.
  @param  EndPoint              The local/remote UDP access point
  @param  IoStatus              The status of the UDP receive
  @param  Context               The opaque parameter to the function.

**/
VOID
EFIAPI
DnsOnPacketSent (
  NET_BUF                   *Packet,
  UDP_END_POINT             *EndPoint,
  EFI_STATUS                IoStatus,
  VOID                      *Context
  )
{
  DNS_INSTANCE              *Instance;
  LIST_ENTRY                *Entry;
  NET_MAP_ITEM              *Item;
  DNS4_TOKEN_ENTRY          *Dns4TokenEntry;
  DNS6_TOKEN_ENTRY          *Dns6TokenEntry;

  Dns4TokenEntry = NULL;
  Dns6TokenEntry = NULL;

  Instance  = (DNS_INSTANCE *) Context;
  NET_CHECK_SIGNATURE (Instance, DNS_INSTANCE_SIGNATURE);

  if (Instance->Service->IpVersion == IP_VERSION_4) {
    NET_LIST_FOR_EACH (Entry, &Instance->Dns4TxTokens.Used) {
      Item = NET_LIST_USER_STRUCT (Entry, NET_MAP_ITEM, Link);
      if (Packet == (NET_BUF *)(Item->Value)) {
        Dns4TokenEntry = ((DNS4_TOKEN_ENTRY *)Item->Key);
        Dns4TokenEntry->PacketToLive = Dns4TokenEntry->Token->RetryInterval;
        break;
      }
    }
  } else {
    NET_LIST_FOR_EACH (Entry, &Instance->Dns6TxTokens.Used) {
      Item = NET_LIST_USER_STRUCT (Entry, NET_MAP_ITEM, Link);
      if (Packet == (NET_BUF *)(Item->Value)) {
        Dns6TokenEntry = ((DNS6_TOKEN_ENTRY *)Item->Key);
        Dns6TokenEntry->PacketToLive = Dns6TokenEntry->Token->RetryInterval;
        break;
      }
    }
  }
  
  NetbufFree (Packet);
}

/**
  Query request information.

  @param  Instance              The DNS instance
  @param  Packet                The packet for querying request information.

  @retval EFI_SUCCESS           Query request information successfully.
  @retval Others                Failed to query request information.

**/
EFI_STATUS
DoDnsQuery (
  IN  DNS_INSTANCE              *Instance,
  IN  NET_BUF                   *Packet
  )
{
  EFI_STATUS      Status;

  //
  // Ready to receive the DNS response.
  //
  if (Instance->UdpIo->RecvRequest == NULL) {
    Status = UdpIoRecvDatagram (Instance->UdpIo, DnsOnPacketReceived, Instance, 0);
    if (EFI_ERROR (Status)) {
      return Status;
    }
  }
  
  //
  // Transmit the DNS packet.
  //
  NET_GET_REF (Packet);

  Status = UdpIoSendDatagram (Instance->UdpIo, Packet, NULL, NULL, DnsOnPacketSent, Instance);
  
  return Status;
}

/**
  Construct the Packet to query Ip.

  @param  Instance              The DNS instance
  @param  HostName              Queried HostName  
  @param  Type                  DNS query Type
  @param  Packet                The packet for querying Ip

  @retval EFI_SUCCESS           The packet is constructed.
  @retval Others                Failed to construct the Packet.

**/
EFI_STATUS
ConstructDNSQueryIp (
  IN  DNS_INSTANCE              *Instance,
  IN  CHAR16                    *HostName,
  IN  UINT16                    Type,
  OUT NET_BUF                   **Packet
  )
{
  NET_FRAGMENT        Frag;
  DNS_HEADER          *DnsHeader;
  CHAR8               *QueryName;
  DNS_QUERY_SECTION   *QuerySection;
  CHAR8               *Header;
  CHAR8               *Tail;
  UINTN               Len;
  UINTN               Index;
  

  Frag.Bulk = AllocatePool (DNS_DEFAULT_BLKSIZE * sizeof (UINT8));
  if (Frag.Bulk == NULL) {
    return EFI_OUT_OF_RESOURCES;
  }

  //
  // Fill header
  //
  DnsHeader = (DNS_HEADER *) Frag.Bulk; 
  DnsHeader->Identification = (UINT16)NET_RANDOM (NetRandomInitSeed());
  DnsHeader->Flags.Uint16 = 0x0000;
  DnsHeader->Flags.Bits.RD = 1;
  DnsHeader->Flags.Bits.OpCode = DNS_FLAGS_OPCODE_STANDARD;
  DnsHeader->Flags.Bits.QR = DNS_FLAGS_QR_QUERY;
  DnsHeader->QuestionsNum = 1;
  DnsHeader->AnswersNum = 0;
  DnsHeader->AuthorityNum = 0;
  DnsHeader->AditionalNum = 0;

  DnsHeader->Identification = HTONS (DnsHeader->Identification);
  DnsHeader->Flags.Uint16 = HTONS (DnsHeader->Flags.Uint16);
  DnsHeader->QuestionsNum = HTONS (DnsHeader->QuestionsNum);
  DnsHeader->AnswersNum = HTONS (DnsHeader->AnswersNum);
  DnsHeader->AuthorityNum = HTONS (DnsHeader->AuthorityNum);
  DnsHeader->AditionalNum = HTONS (DnsHeader->AditionalNum);

  Frag.Len = sizeof (*DnsHeader);

  //
  // Fill Query name
  //
  QueryName = (CHAR8 *) (Frag.Bulk + Frag.Len);
  Header = QueryName;
  Tail = Header + 1;
  Len = 0;
  for (Index = 0; HostName[Index] != 0; Index++) {
    *Tail = (CHAR8) HostName[Index];
    if (*Tail == '.') {
      *Header = (CHAR8) Len;
      Header = Tail;
      Tail ++;
      Len = 0;
    } else {
      Tail++;
      Len++;
    }
  }
  *Header = (CHAR8) Len;
  *Tail = 0;
  Frag.Len = (UINT32) (Frag.Len + StrLen (HostName) + 2); /// 1 for header, 1 for tail.

  //
  // Rest query section
  //
  QuerySection = (DNS_QUERY_SECTION *) (Frag.Bulk + Frag.Len);
  QuerySection->Type = Type;
  QuerySection->Class = DNS_CLASS_INET;

  QuerySection->Type = HTONS (QuerySection->Type);
  QuerySection->Class = HTONS (QuerySection->Class);

  Frag.Len += sizeof (*QuerySection);

  //
  // Wrap the Frag in a net buffer.
  //
  *Packet = NetbufFromExt (&Frag, 1, 0, 0, DnsDummyExtFree, NULL);
  if (*Packet == NULL) {
    FreePool (Frag.Bulk);
    return EFI_OUT_OF_RESOURCES;
  }
  
  //
  // Store the UdpIo in ProtoData.
  //
  *((UINTN *) &((*Packet)->ProtoData[0])) = (UINTN) (Instance->UdpIo);

  return EFI_SUCCESS;
}

/**
  Retransmit the packet.

  @param  Instance              The DNS instance
  @param  Packet                Retransmit the packet 

  @retval EFI_SUCCESS           The packet is retransmitted.
  @retval Others                Failed to retransmit.

**/
EFI_STATUS
DnsRetransmit (
  IN DNS_INSTANCE        *Instance,
  IN NET_BUF             *Packet
  )
{
  EFI_STATUS      Status;
  
  UINT8           *Buffer;

  ASSERT (Packet != NULL);

  //
  // Set the requests to the listening port, other packets to the connected port
  //
  Buffer = NetbufGetByte (Packet, 0, NULL);
  ASSERT (Buffer != NULL);

  NET_GET_REF (Packet);

  Status = UdpIoSendDatagram (
             Instance->UdpIo,
             Packet,
             NULL,
             NULL,
             DnsOnPacketSent,
             Instance
             );

  if (EFI_ERROR (Status)) {
    NET_PUT_REF (Packet);
  }

  return Status;
}

/**
  The timer ticking function for the DNS services.

  @param  Event                 The ticking event
  @param  Context               The DNS service instance

**/
VOID
EFIAPI
DnsOnTimerRetransmit (
  IN EFI_EVENT              Event,
  IN VOID                   *Context
  )
{
  DNS_SERVICE                *Service;

  LIST_ENTRY                 *Entry;
  LIST_ENTRY                 *Next;

  DNS_INSTANCE               *Instance;
  LIST_ENTRY                 *EntryNetMap;
  NET_MAP_ITEM               *ItemNetMap;
  DNS4_TOKEN_ENTRY           *Dns4TokenEntry;
  DNS6_TOKEN_ENTRY           *Dns6TokenEntry;

  Dns4TokenEntry = NULL;
  Dns6TokenEntry = NULL;

  Service = (DNS_SERVICE *) Context;


  if (Service->IpVersion == IP_VERSION_4) {
    //
    // Iterate through all the children of the DNS service instance. Time
    // out the packet. If maximum retries reached, clean the Token up.
    //
    NET_LIST_FOR_EACH_SAFE (Entry, Next, &Service->Dns4ChildrenList) {
      Instance = NET_LIST_USER_STRUCT (Entry, DNS_INSTANCE, Link);

      EntryNetMap = Instance->Dns4TxTokens.Used.ForwardLink;
      while (EntryNetMap != &Instance->Dns4TxTokens.Used) {
        ItemNetMap = NET_LIST_USER_STRUCT (EntryNetMap, NET_MAP_ITEM, Link);
        Dns4TokenEntry = (DNS4_TOKEN_ENTRY *)(ItemNetMap->Key);
        if (Dns4TokenEntry->PacketToLive == 0 || (--Dns4TokenEntry->PacketToLive > 0)) {
          EntryNetMap = EntryNetMap->ForwardLink;
          continue;
        }

        //
        // Retransmit the packet if haven't reach the maxmium retry count,
        // otherwise exit the transfer.
        //
        if (++Dns4TokenEntry->Token->RetryCount < Instance->MaxRetry) {
          DnsRetransmit (Instance, (NET_BUF *)ItemNetMap->Value);
          EntryNetMap = EntryNetMap->ForwardLink;
        } else {
          //
          // Maximum retries reached, clean the Token up.
          //
          Dns4RemoveTokenEntry (&Instance->Dns4TxTokens, Dns4TokenEntry);
          Dns4TokenEntry->Token->Status = EFI_TIMEOUT;
          gBS->SignalEvent (Dns4TokenEntry->Token->Event);
          DispatchDpc ();
          
          //
          // Free the sending packet.
          //
          if (ItemNetMap->Value != NULL) {
            NetbufFree ((NET_BUF *)(ItemNetMap->Value));
          }

          EntryNetMap = Instance->Dns4TxTokens.Used.ForwardLink;
        }
      }
    } 
  }else {
    //
    // Iterate through all the children of the DNS service instance. Time
    // out the packet. If maximum retries reached, clean the Token up.
    //
    NET_LIST_FOR_EACH_SAFE (Entry, Next, &Service->Dns6ChildrenList) {
      Instance = NET_LIST_USER_STRUCT (Entry, DNS_INSTANCE, Link);
      
      EntryNetMap = Instance->Dns6TxTokens.Used.ForwardLink;
      while (EntryNetMap != &Instance->Dns6TxTokens.Used) {
        ItemNetMap = NET_LIST_USER_STRUCT (EntryNetMap, NET_MAP_ITEM, Link);
        Dns6TokenEntry = (DNS6_TOKEN_ENTRY *) (ItemNetMap->Key);
        if (Dns6TokenEntry->PacketToLive == 0 || (--Dns6TokenEntry->PacketToLive > 0)) {
          EntryNetMap = EntryNetMap->ForwardLink;
          continue;
        }

        //
        // Retransmit the packet if haven't reach the maxmium retry count,
        // otherwise exit the transfer.
        //
        if (++Dns6TokenEntry->Token->RetryCount < Instance->MaxRetry) {
          DnsRetransmit (Instance, (NET_BUF *) ItemNetMap->Value);
          EntryNetMap = EntryNetMap->ForwardLink;
        } else {
          //
          // Maximum retries reached, clean the Token up.
          //
          Dns6RemoveTokenEntry (&Instance->Dns6TxTokens, Dns6TokenEntry);
          Dns6TokenEntry->Token->Status = EFI_TIMEOUT;
          gBS->SignalEvent (Dns6TokenEntry->Token->Event);
          DispatchDpc ();
          
          //
          // Free the sending packet.
          //
          if (ItemNetMap->Value != NULL) {
            NetbufFree ((NET_BUF *) (ItemNetMap->Value));
          }

          EntryNetMap = Instance->Dns6TxTokens.Used.ForwardLink;
        } 
      }
    }
  } 
}

/**
  The timer ticking function for the DNS driver.

  @param  Event                 The ticking event
  @param  Context               NULL

**/
VOID
EFIAPI
DnsOnTimerUpdate (
  IN EFI_EVENT              Event,
  IN VOID                   *Context
  )
{
  LIST_ENTRY                 *Entry;
  LIST_ENTRY                 *Next;
  DNS4_CACHE                 *Item4;
  DNS6_CACHE                 *Item6;

  Item4 = NULL;
  Item6 = NULL;

  //
  // Iterate through all the DNS4 cache list.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, &mDriverData->Dns4CacheList) {
    Item4 = NET_LIST_USER_STRUCT (Entry, DNS4_CACHE, AllCacheLink);
    Item4->DnsCache.Timeout--;
  }
  
  Entry = mDriverData->Dns4CacheList.ForwardLink;
  while (Entry != &mDriverData->Dns4CacheList) {
    Item4 = NET_LIST_USER_STRUCT (Entry, DNS4_CACHE, AllCacheLink);
    if (Item4->DnsCache.Timeout<=0) {
      RemoveEntryList (&Item4->AllCacheLink);
      Entry = mDriverData->Dns4CacheList.ForwardLink;
    } else {
      Entry = Entry->ForwardLink;
    }
  }
  
  //
  // Iterate through all the DNS6 cache list.
  //
  NET_LIST_FOR_EACH_SAFE (Entry, Next, &mDriverData->Dns6CacheList) {
    Item6 = NET_LIST_USER_STRUCT (Entry, DNS6_CACHE, AllCacheLink);
    Item6->DnsCache.Timeout--;
  }
  
  Entry = mDriverData->Dns6CacheList.ForwardLink;
  while (Entry != &mDriverData->Dns6CacheList) {
    Item6 = NET_LIST_USER_STRUCT (Entry, DNS6_CACHE, AllCacheLink);
    if (Item6->DnsCache.Timeout<=0) {
      RemoveEntryList (&Item6->AllCacheLink);
      Entry = mDriverData->Dns6CacheList.ForwardLink;
    } else {
      Entry = Entry->ForwardLink;
    }
  }
}

